﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Security.Cryptography;
using System.Text;

namespace GoodStuff.Security
{
    /// <summary>
    /// Allows you to store and check passwords in a secure way using a one-way encryption with hash and salt. Using this class will ensure that every password will receive a new random salt.
    /// </summary>
    /// <remarks>
    /// SecurePassword uses SHA1 encryption to create a one-way hash from a clear text password string. The generated saltedHash is a 32 character string. To properly store this string in a database, you might want to convert this string into a hexadecimal string.
    /// </remarks>
    /// <example>
    /// The following example shows how to create and verify a secure password.
    /// <code>
    /// void passwordSample()
    /// {
    ///     string passwordText = "helloWorld";
    ///     
    ///     // create a secure password from a readable string. A new salt is generated automatically.
    ///     SecurePassword pw = new SecurePassword(passwordText);
    ///     
    ///     // remember the hash and salt in a database.
    ///     string hash = pw.SaltedHash;
    ///     int salt = pw.Salt;
    ///     
    ///     // to verify the validity of the password, reconstruct the securepassword using the salt and hash and verify the password.
    ///     SecurePassword verifyPassword = new SecurePassword(hash, salt);
    ///     bool check1 = verifyPassword.Validate("Invalidpassword");  //returns FALSE;
    ///     bool check2 = verifyPassword.Validate("helloWorld"); // returns TRUE;
    /// }
    /// </code>
    /// </example>
    public sealed class SecurePassword
    {
        private string _saltedHash;
		private int _salt;

		/// <summary>
		/// Re-creates a password structure from a previous saved password.
		/// </summary>
		/// <param name="saltedHash">The saltedHash value obtained by a previous SecurePassword instance</param>
		/// <param name="salt">The randomized salt</param>
		public SecurePassword(string saltedHash, int salt)
		{
			_saltedHash = saltedHash;
			_salt = salt;
		}

		/// <summary>
		/// Create a new password for storage. After construction, use the SaltedHash and Salt 
		/// properties to store the password details.
		/// </summary>
		/// <param name="password"></param>
        public SecurePassword(string password)
		{
			_salt = CreateRandomSalt();
			_saltedHash = ComputeSaltedHash(password);
		}

		/// <summary>
		/// Test if the given password is strong. 
		/// </summary>
		/// <param name="password"></param>
		/// <returns></returns>
		public static bool IsStrong(string password)
		{
			return IsStrong(password, 8);
		}

		/// <summary>
		/// Tests if the given password is strong and is at least <c>minimumLenght</c> characters long.
		/// </summary>
		/// <remarks>
		/// In this class, a strong password is not null, at least n character, contains lowercase and uppercase characters
		/// and contains at least one non-alphabetic character.
		/// </remarks>
		/// <param name="password"></param>
		/// <param name="minimumlength"></param>
		/// <returns></returns>
		public static bool IsStrong(string password, int minimumlength)
		{
			if(password == null)
				return false;

			if(password.Length < minimumlength)
				return false;

			if(password.IndexOfAny("|`~0123456789!@#$%^&*(),./?><;':\"{}[]_+-=".ToCharArray()) < 0)
				return false;

			if(password.ToLower() == password)
				return false;

			if(password.ToUpper() == password)
				return false;

			return true;
		}

		/// <summary>
		/// Returns the Salt for the current password for storage in a database.
		/// </summary>
		public int Salt
		{
			get { return _salt; }
		}

		/// <summary>
		/// Returns the salted hash for the current password for storage in a database.
		/// </summary>
		public string SaltedHash
		{
			get { return _saltedHash; }
		}
	
		/// <summary>
		/// Validate the given password against the stored salt & hash.
		/// </summary>
		/// <param name="password"></param>
		/// <returns></returns>
		public bool Validate(string password)
		{
			if(ComputeSaltedHash(password) == _saltedHash)
				return true;
			else
				return false;
		}

		/// <summary>
		/// Creates a random password.
		/// </summary>
		/// <param name="PasswordLength"></param>
		/// <remarks>
		/// The generated password is random, but may not be strong enough to pass <see cref="IsStrong"/>
		/// </remarks>
		/// <returns>A random password of at least <see cref="PasswordLength"/> characters.</returns>
		public static string CreateRandomPassword(int passwordLength)
		{
            string result;
            do
            {
                String _allowedChars = "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ1234567890";
                Byte[] randomBytes = new Byte[passwordLength];
                RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
                rng.GetBytes(randomBytes);
                char[] chars = new char[passwordLength];
                int allowedCharCount = _allowedChars.Length;

                for (int i = 0; i < passwordLength; i++)
                {
                    chars[i] = _allowedChars[(int)randomBytes[i] % allowedCharCount];
                }

                result = new string(chars);                
            } while (!IsStrong(result));

            return result;
		}

        /// <summary>
        /// internal method to compute a salted hash from a password.
        /// </summary>
        /// <param name="password"></param>
        /// <returns></returns>
		private string ComputeSaltedHash(string password)
		{
			// Create Byte array of password string
			ASCIIEncoding encoder = new ASCIIEncoding();
			Byte[] _secretBytes = encoder.GetBytes(password);
      
			// Create a new salt
			Byte[] _saltBytes = new Byte[4];
			_saltBytes[0] = (byte)(_salt >> 24);
			_saltBytes[1] = (byte)(_salt >> 16);
			_saltBytes[2] = (byte)(_salt >> 8);
			_saltBytes[3] = (byte)(_salt);

			// append the two arrays
			Byte[] toHash = new Byte[_secretBytes.Length + _saltBytes.Length];
			Array.Copy(_secretBytes, 0, toHash, 0, _secretBytes.Length);
			Array.Copy(_saltBytes, 0, toHash, _secretBytes.Length, _saltBytes.Length);

			SHA1 sha1 = SHA1.Create();
			Byte[] computedHash = sha1.ComputeHash(toHash);

			return encoder.GetString(computedHash);
		}

		private static int CreateRandomSalt()
		{
			Byte[] _saltBytes = new Byte[4];
			RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
			rng.GetBytes(_saltBytes);

			return ((((int)_saltBytes[0]) << 24) + (((int)_saltBytes[1]) << 16) + 
				(((int)_saltBytes[2]) << 8) + ((int)_saltBytes[3]));

		}
	}
}
